#include <SoftwareSerial.h>
#include <Wire.h>
#include "DFRobot_CH423.h"
#include <ModbusRTU.h>

// 定时任务（与miniiot无关，用于实现长按恢复出厂设置）
#include <Ticker.h>

#include "defineConfig.h"
#include "AppConfig.h"


// 恢复出厂设置计数(秒)
int sys_rst_count = 0;

Ticker TickerRST;

// 应用配置读取
AppConfig appConfigObj("/AppConfig.json");

DFRobot_CH423 ch423(Wire);
SoftwareSerial mySerial(PIN_RS485_RX, PIN_RS485_TX);
ModbusRTU myModbus;

int is_reboot = 0;
int is_init = 0;
int rs485_id = 0;
int rs485_baud = 0;
int rs485_update = 0;

int switch_1 = 0;
int switch_2 = 0;
int switch_3 = 0;
int switch_4 = 0;
int switch_5 = 0;
int switch_6 = 0;
int switch_7 = 0;

int switch_11 = 0;
int switch_12 = 0;
int switch_13 = 0;
int switch_14 = 0;

int switch_1_last = 0;
int switch_2_last = 0;
int switch_3_last = 0;
int switch_4_last = 0;
int switch_5_last = 0;
int switch_6_last = 0;
int switch_7_last = 0;

int switch_11_last = 0;
int switch_12_last = 0;
int switch_13_last = 0;
int switch_14_last = 0;


// 按5秒开发板上的按钮恢复出厂设置
void SysRstfun()
{
    if (digitalRead(SYS_RST_IO) == LOW)
    {
        sys_rst_count++;
    }
    else
    {
        sys_rst_count = 0;
    }
    if (sys_rst_count >= 5)
    {
        // 格式化存储
        MiniIot.RESET();
    }
}


// 重启回调
uint16_t reboot(TRegister* reg, uint16_t val) {
  if(COIL_BOOL(val)){
    is_reboot = 1;
  }
  return val;
}

// rs485_id回调
uint16_t cb_rs485_id(TRegister* reg, uint16_t val) {
  rs485_id = val;
  rs485_update = 1;
  return val;
}

// rs485_baud回调
uint16_t cb_rs485_baud(TRegister* reg, uint16_t val) {
  if(val == 1){
    rs485_baud =  9600;
  }
  if(val == 2){
    rs485_baud =  19200;
  }
  rs485_update = 1;
  return val;
}

// 晶体管回调
uint16_t cb1(TRegister* reg, uint16_t val) {

  if(reg->address.address == 0x01){
    ch423.digitalWrite(PIN_Y1, !COIL_BOOL(val));
    switch_1 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x02){
    ch423.digitalWrite(PIN_Y2, !COIL_BOOL(val));
    switch_2 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x03){
    ch423.digitalWrite(PIN_Y3, !COIL_BOOL(val));
    switch_3 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x04){
    ch423.digitalWrite(PIN_Y4, !COIL_BOOL(val));
    switch_4 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x05){
    ch423.digitalWrite(PIN_Y5, !COIL_BOOL(val));
    switch_5 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x06){
    ch423.digitalWrite(PIN_Y6, !COIL_BOOL(val));
    switch_6 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x07){
    ch423.digitalWrite(PIN_Y7, !COIL_BOOL(val));
    switch_7 = COIL_BOOL(val);
  }

  return val;
}

// 继电器回调
uint16_t cb2(TRegister* reg, uint16_t val) {

  if(reg->address.address == 0x11){
    ch423.digitalWrite(PIN_Y11, !COIL_BOOL(val));
    switch_11 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x12){
    ch423.digitalWrite(PIN_Y12, !COIL_BOOL(val));
    switch_12 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x13){
    ch423.digitalWrite(PIN_Y13, !COIL_BOOL(val));
    switch_13 = COIL_BOOL(val);
  }
  if(reg->address.address == 0x14){
    ch423.digitalWrite(PIN_Y14, !COIL_BOOL(val));
    switch_14 = COIL_BOOL(val);
  }

  return val;
}

// ping
void ping() {

  JSONVar myObject;
  myObject["switch_1"] = (String)switch_1;
  myObject["switch_2"] = (String)switch_2;
  myObject["switch_3"] = (String)switch_3;
  myObject["switch_4"] = (String)switch_4;
  myObject["switch_5"] = (String)switch_5;
  myObject["switch_6"] = (String)switch_6;
  myObject["switch_7"] = (String)switch_7;

  MiniIot.propertyPost(myObject);

  JSONVar myObject2;
  myObject2["switch_11"] = (String)switch_11;
  myObject2["switch_12"] = (String)switch_12;
  myObject2["switch_13"] = (String)switch_13;
  myObject2["switch_14"] = (String)switch_14;

  MiniIot.propertyPost(myObject2);

  JSONVar myObject3;
  myObject3["rs485_id"] = (String)rs485_id;

  if(rs485_baud == 9600){
    myObject3["rs485_baud"] = "1";
  }
  if(rs485_baud == 19200){
    myObject3["rs485_baud"] = "2";
  }

  MiniIot.propertyPost(myObject3);

  Serial.printf("剩余内存: %d B\n", ESP.getFreeHeap());
}

// 业务回调函数
void ServiceCallbackFunction(JsonObject dataObj)
{
    // 所有参数默认都是以String类型返回，需要自行转换成目标类型
    // json库使用ArduinoJson
    String serviceName = dataObj["serviceName"].as<String>();
    
    // Ping
    if(serviceName == "miniiot_ping"){
      ping();
    }

    if(serviceName == "on_all"){
      myModbus.Coil(0x01, true);
      myModbus.Coil(0x02, true);
      myModbus.Coil(0x03, true);
      myModbus.Coil(0x04, true);
      myModbus.Coil(0x05, true);
      myModbus.Coil(0x06, true);
      myModbus.Coil(0x07, true);

      myModbus.Coil(0x11, true);
      myModbus.Coil(0x12, true);
      myModbus.Coil(0x13, true);
      myModbus.Coil(0x14, true);
    }
    
    if(serviceName == "off_all"){
      myModbus.Coil(0x01, false);
      myModbus.Coil(0x02, false);
      myModbus.Coil(0x03, false);
      myModbus.Coil(0x04, false);
      myModbus.Coil(0x05, false);
      myModbus.Coil(0x06, false);
      myModbus.Coil(0x07, false);

      myModbus.Coil(0x11, false);
      myModbus.Coil(0x12, false);
      myModbus.Coil(0x13, false);
      myModbus.Coil(0x14, false);
    }
    
    if(serviceName == "on_one"){
      int id = dataObj["serviceParams"]["id"].as<int>();
      if(id > 10){
        id = id + 6;
      }
      myModbus.Coil(id, true);
    }

    if(serviceName == "off_one"){
      int id = dataObj["serviceParams"]["id"].as<int>();
      if(id > 10){
        id = id + 6;
      }
      myModbus.Coil(id, false);
    }

    if(serviceName == "rs485_set"){
      int id = dataObj["serviceParams"]["rs485_id"].as<int>();
      int baud = dataObj["serviceParams"]["rs485_baud"].as<int>();

      myModbus.Hreg(0x20, id);
      myModbus.Hreg(0x21, baud);
    }
}


void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println();

  // 配置文件加载
  appConfigObj.loadConfig();
  rs485_id = appConfigObj.rs485_id_Get();
  rs485_baud = appConfigObj.rs485_baud_Get();

  // IO扩展初始化
  Wire.begin(PIN_SDA, PIN_SCL);
  ch423.begin();
  
  // 推挽输出
  ch423.pinMode(ch423.eGPO, ch423.ePUSH_PULL);
  // 全部输出高电平
  ch423.digitalWrite(ch423.eGPOTotal, 0xFF);

  // RS485串口
  mySerial.begin(rs485_baud, SWSERIAL_8N1);
  // 初始化Modbus RTU
  myModbus.begin(&mySerial);
  myModbus.setBaudrate(rs485_baud);
  myModbus.slave(rs485_id);

  // 注册Modbus RTU回调函数
  // 重启
  myModbus.addCoil(0x00, false, 1);
  myModbus.onSetCoil(0x00, reboot, 1);

  // 晶体管
  myModbus.addCoil(0x01, false, 7);
  myModbus.onSetCoil(0x01, cb1, 7);

  // 继电器
  myModbus.addCoil(0x11, false, 4);
  myModbus.onSetCoil(0x11, cb2, 4);

  // 485参数
  myModbus.addHreg(0x20, rs485_id, 1);
  myModbus.onSetHreg(0x20, cb_rs485_id, 1);

  if(rs485_baud == 9600){
    myModbus.addHreg(0x21, 1, 1);
  }
  if(rs485_baud == 19200){
    myModbus.addHreg(0x21, 2, 1);
  }
  myModbus.onSetHreg(0x21, cb_rs485_baud, 1);


  pinMode(SYS_RST_IO, INPUT_PULLUP);

  // 绑定定时任务回调函数
  TickerRST.attach(1, SysRstfun);

  // 使用产品密钥初始化MiniIot，（产品ID，产品密钥）
  // 设备不存在会自动注册添加设备
  MiniIot.begin("ZnnQ2FTH", "h5Tmn1l3m669DY2I");

  // 使用设备密钥初始化MiniIot，（产品ID，设备ID，设备密钥）
  // 必须先在平台上添加设备
  // MiniIot.begin("NshSsd7w", "JZM5xv8sWi", "awZUYl2CsRHqdlaT");

  // 绑定回调函数
  MiniIot.attach(ServiceCallbackFunction);

  Serial.printf("剩余内存: %d B\n", ESP.getFreeHeap());
  Serial.print(F("cpu运行频率:"));Serial.println(ESP.getCpuFreqMHz());
  Serial.print(F("当前固件大小:"));Serial.println(ESP.getSketchSize());
  Serial.print(F("剩余固件空间:"));Serial.println(ESP.getFreeSketchSpace());

}

void loop()
{
  myModbus.task();
  MiniIot.loop();
  
  if(is_reboot == 1){
    Serial.println("\n[SYSTEM] 重启中...\n");
    #ifdef ESP8266
        ESP.reset();
    #endif
    #ifdef ESP32
        ESP.restart();
    #endif
  }

  // 服务器连接成功
  if(MiniIot.running()){
      digitalWrite(MiniIot_STATE_LED, 1);
      // 不能直接使用delay延时
      // MiniIot.delay(3000);

      // Serial.printf("剩余内存: %dB\n", ESP.getFreeHeap());

      // 首次连接，上报设备信息
      if(is_init == 0){
        is_init = 1;
        ping();
      }

      int is_push_1 = 0;
      JSONVar myObject;
      if(switch_1 != switch_1_last){
        switch_1_last = switch_1;
        is_push_1 = 1;
        myObject["switch_1"] = (String)switch_1;
      }
      if(switch_2 != switch_2_last){
        switch_2_last = switch_2;
        is_push_1 = 1;
        myObject["switch_2"] = (String)switch_2;
      }
      if(switch_3 != switch_3_last){
        switch_3_last = switch_3;
        is_push_1 = 1;
        myObject["switch_3"] = (String)switch_3;
      }
      if(switch_4 != switch_4_last){
        switch_4_last = switch_4;
        is_push_1 = 1;
        myObject["switch_4"] = (String)switch_4;
      }
      if(switch_5 != switch_5_last){
        switch_5_last = switch_5;
        is_push_1 = 1;
        myObject["switch_5"] = (String)switch_5;
      }
      if(switch_6 != switch_6_last){
        switch_6_last = switch_6;
        is_push_1 = 1;
        myObject["switch_6"] = (String)switch_6;
      }
      if(switch_7 != switch_7_last){
        switch_7_last = switch_7;
        is_push_1 = 1;
        myObject["switch_7"] = (String)switch_7;
      }

      if(is_push_1 == 1){
        MiniIot.propertyPost(myObject);
      }

      int is_push_2 = 0;
      JSONVar myObject2;
      if(switch_11 != switch_11_last){
        switch_11_last = switch_11;
        is_push_2 = 1;
        myObject2["switch_11"] = (String)switch_11;
      }
      if(switch_12 != switch_12_last){
        switch_12_last = switch_12;
        is_push_2 = 1;
        myObject2["switch_12"] = (String)switch_12;
      }
      if(switch_13 != switch_13_last){
        switch_13_last = switch_13;
        is_push_2 = 1;
        myObject2["switch_13"] = (String)switch_13;
      }
      if(switch_14 != switch_14_last){
        switch_14_last = switch_14;
        is_push_2 = 1;
        myObject2["switch_14"] = (String)switch_14;
      }
      
      if(is_push_2 == 1){
        MiniIot.propertyPost(myObject2);
      }



      if(rs485_update == 1){
        appConfigObj.rs485_id_Set(rs485_id);
        appConfigObj.rs485_baud_Set(rs485_baud);

        JSONVar myObject3;
        myObject3["rs485_id"] = (String)rs485_id;

        if(rs485_baud == 9600){
          myObject3["rs485_baud"] = "1";
        }
        if(rs485_baud == 19200){
          myObject3["rs485_baud"] = "2";
        }

        MiniIot.propertyPost(myObject3);

        rs485_update = 0;
      }
  }
}

